using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Reflection;
using Apache.Cassandra;
using Cassandraemon.Util;


namespace Cassandraemon
{

	class DictionaryCassandraEntityConverter<TInput, TOutput, TOutputKey, TOutputValue> : CassandraEntityConverterBase<TInput, TOutput> where TOutput : IDictionary<TOutputKey, TOutputValue>
	{
		
		public DictionaryCassandraEntityConverter (CassandraEntity<TInput> entity) : base(entity) { }
		
		
		public TOutput ToFlatDictionary()
		{
			if(!MappingUtil.IsValueTypeOrString(typeof(TOutputKey)) || !MappingUtil.IsValueTypeOrString(typeof(TOutputValue)))
			{
				throw new InvalidOperationException("You should specify valuetype dictionary when use ToFlatDictionary.");
			}
			
			if(typeof(TInput).Equals(typeof(CounterColumn)) ||
			   typeof(TInput).Equals(typeof(CounterSuperColumn)) ||
			   typeof(TInput).Equals(typeof(List<CounterColumn>)) ||
			   typeof(TInput).Equals(typeof(List<CounterSuperColumn>)))
			{
				if(!typeof(TOutputValue).Equals(typeof(long)))
				{
					throw new InvalidOperationException("You should specify long type when you get CounterColumn and use ToFlatDictionary.");
				}
			}

			if(Entity.Data == null) return Object;
			
			var kvList = GetFlatKeyValueList<TOutputKey, TOutputValue>(Entity.Data);
			
			if(kvList == null) return Object;
			if(kvList.Count == 0) return Object;
			
			var dictionary = new Dictionary<TOutputKey, TOutputValue>();
			kvList.ForEach(kv => dictionary.Add(kv.Key, kv.Value));
			return (TOutput)(object)dictionary;
		}
		
		public TOutput ToFlatValueKeyDictionary()
		{
			if(!MappingUtil.IsValueTypeOrString(typeof(TOutputKey)) || !MappingUtil.IsValueTypeOrString(typeof(TOutputValue)))
			{
				throw new InvalidOperationException("You should specify valuetype dictionary when use ToFlatValueKeyDictionary.");
			}

			if(typeof(TInput).Equals(typeof(CounterColumn)) ||
			   typeof(TInput).Equals(typeof(CounterSuperColumn)) ||
			   typeof(TInput).Equals(typeof(List<CounterColumn>)) ||
			   typeof(TInput).Equals(typeof(List<CounterSuperColumn>)))
			{
				if(!typeof(TOutputKey).Equals(typeof(long)))
				{
					throw new InvalidOperationException("You should specify long type when you get CounterColumn and use ToFlatValueKeyDictionary.");
				}
			}

			if(Entity.Data == null) return Object;
			
			var kvList = GetFlatKeyValueList<TOutputValue, TOutputKey>(Entity.Data);
			
			if(kvList == null) return Object;
			if(kvList.Count == 0) return Object;
			
			var dictionary = new Dictionary<TOutputKey, TOutputValue>();
			kvList.ForEach(kv => dictionary.Add(kv.Value, kv.Key));
			return (TOutput)(object)dictionary;
		}
		
		
		public TOutput ToObjectDictionary()
		{
			if(!MappingUtil.IsValueTypeOrString(typeof(TOutputKey)))
			{
				throw new InvalidOperationException("You should specify valuetype dictionary key when use ToDictionary.");
			}
			
			if(typeof(TInput).Equals(typeof(CounterColumn)) ||
			   typeof(TInput).Equals(typeof(CounterSuperColumn)) ||
			   typeof(TInput).Equals(typeof(List<CounterColumn>)) ||
			   typeof(TInput).Equals(typeof(List<CounterSuperColumn>)))
			{
				throw new InvalidOperationException("You can't use ToObjectDictionary when you get CounterColumn.");
			}

			if(Entity.Data == null) return Object;
			
			var dic = GetNestedColumnDictionary<TOutputKey>(Entity.Data);
			
			if(dic == null) return Object;
			if(dic.Count == 0) return Object;
			
			var dictionary = new Dictionary<TOutputKey, TOutputValue>();
			foreach(var key in dic.Keys)
			{
				dictionary.Add(key, MappingUtil.MappingObject<TOutputValue>(dic[key]));
			}
			
			return (TOutput)(object)dictionary;
		}
		
		
		public TOutput ToNameListDictionary<TListItem>()
		{
			if(!MappingUtil.IsValueTypeOrString(typeof(TOutputKey)))
			{
				throw new InvalidOperationException("You should specify valuetype for TOutputKey when use ToNameListDictionary.");
			}
			
			if(typeof(TOutputValue).GetInterface("IEnumerable") == null)
			{
				throw new InvalidOperationException("You should specify collection for TOutputValue when use ToNameListDictionary.");
			}
			
			if(!MappingUtil.IsValueTypeOrString(typeof(TListItem)))
			{
				throw new InvalidOperationException("You should specify valuetype for TListItem when use ToNameListDictionary.");
			}
			
			if(Entity.Data == null) return Object;
			
			var dic = GetNestedNameListDictionary<TOutputKey, TListItem>(Entity.Data);
			
			if(dic == null) return Object;
			if(dic.Count == 0) return Object;
			
			return (TOutput)(object)dic;
		}
		
		public TOutput ToValueListDictionary<TListItem>()
		{
			if(!MappingUtil.IsValueTypeOrString(typeof(TOutputKey)))
			{
				throw new InvalidOperationException("You should specify valuetype for TOutputKey when use ToValueListDictionary.");
			}
			
			if(typeof(TOutputValue).GetInterface("IEnumerable") == null)
			{
				throw new InvalidOperationException("You should specify collection for TOutputValue when use ToValueListDictionary.");
			}
			
			if(!MappingUtil.IsValueTypeOrString(typeof(TListItem)))
			{
				throw new InvalidOperationException("You should specify valuetype for TListItem when use ToValueListDictionary.");
			}
			
			if(typeof(TInput).Equals(typeof(CounterColumn)) ||
			   typeof(TInput).Equals(typeof(CounterSuperColumn)) ||
			   typeof(TInput).Equals(typeof(List<CounterColumn>)) ||
			   typeof(TInput).Equals(typeof(List<CounterSuperColumn>)))
			{
				if(!typeof(TListItem).Equals(typeof(long)))
				{
					throw new InvalidOperationException("You should specify long type when you get CounterColumn and use ToValueListDictionary.");
				}
			}

			if(Entity.Data == null) return Object;
			
			var dic = GetNestedValueListDictionary<TOutputKey, TListItem>(Entity.Data);
			
			if(dic == null) return Object;
			if(dic.Count == 0) return Object;
			
			return (TOutput)(object)dic;
		}
		
		public TOutput ToDictionaryDictionary<TDictionaryKey, TDictionaryValue>()
		{
			if(!MappingUtil.IsValueTypeOrString(typeof(TOutputKey)))
			{
				throw new InvalidOperationException("You should specify valuetype for TOutputKey when use ToDictionaryDictionary.");
			}
			
			if(typeof(TOutputValue).GetInterface("IDictionary") == null)
			{
				throw new InvalidOperationException("You should specify dictionary for TOutputValue when use ToDictionaryDictionary.");
			}
			
			if(!MappingUtil.IsValueTypeOrString(typeof(TDictionaryKey)) || !MappingUtil.IsValueTypeOrString(typeof(TDictionaryValue)))
			{
				throw new InvalidOperationException("You should specify valuetype for TDictionaryKey and TDictionaryValue when use ToDictionaryDictionary.");
			}
			
			if(typeof(TInput).Equals(typeof(CounterColumn)) ||
			   typeof(TInput).Equals(typeof(CounterSuperColumn)) ||
			   typeof(TInput).Equals(typeof(List<CounterColumn>)) ||
			   typeof(TInput).Equals(typeof(List<CounterSuperColumn>)))
			{
				if(!typeof(TDictionaryValue).Equals(typeof(long)))
				{
					throw new InvalidOperationException("You should specify long type when you get CounterColumn and use ToDictionaryDictionary.");
				}
			}

			if(Entity.Data == null) return Object;
			
			var dic = GetNestedKeyValuePairListDictionary<TOutputKey, TDictionaryKey, TDictionaryValue>(Entity.Data);
			
			if(dic == null) return Object;
			if(dic.Count == 0) return Object;
			
			return (TOutput)(object)dic.ToDictionary(kv => kv.Key, kv => kv.Value.ToDictionary(ikv => ikv.Key, ikv => ikv.Value));
		}
		
		public TOutput ToValueKeyDictionaryDictionary<TDictionaryKey, TDictionaryValue>()
		{
			if(!MappingUtil.IsValueTypeOrString(typeof(TOutputKey)))
			{
				throw new InvalidOperationException("You should specify valuetype for TOutputKey when use ToValueKeyDictionaryDictionary.");
			}
			
			if(typeof(TOutputValue).GetInterface("IDictionary") == null)
			{
				throw new InvalidOperationException("You should specify dictionary for TOutputValue when use ToValueKeyDictionaryDictionary.");
			}
			
			if(!MappingUtil.IsValueTypeOrString(typeof(TDictionaryKey)) || !MappingUtil.IsValueTypeOrString(typeof(TDictionaryValue)))
			{
				throw new InvalidOperationException("You should specify valuetype for TDictionaryKey and TDictionaryValue when use ToValueKeyDictionaryDictionary.");
			}
			
			if(typeof(TInput).Equals(typeof(CounterColumn)) ||
			   typeof(TInput).Equals(typeof(CounterSuperColumn)) ||
			   typeof(TInput).Equals(typeof(List<CounterColumn>)) ||
			   typeof(TInput).Equals(typeof(List<CounterSuperColumn>)))
			{
				if(!typeof(TDictionaryKey).Equals(typeof(long)))
				{
					throw new InvalidOperationException("You should specify long type when you get CounterColumn and use ToValueKeyDictionaryDictionary.");
				}
			}

			if(Entity.Data == null) return Object;
			
			var dic = GetNestedKeyValuePairListDictionary<TOutputKey, TDictionaryValue, TDictionaryKey>(Entity.Data);
			
			if(dic == null) return Object;
			if(dic.Count == 0) return Object;
			
			return (TOutput)(object)dic.ToDictionary(kv => kv.Key, kv => kv.Value.ToDictionary(ikv => ikv.Value, ikv => ikv.Key));
		}
	}
}
